
package no.cicero.testframework.reporting.reports;

import no.cicero.testframework.execution.exceptions.ExecutionException;
import no.cicero.testframework.execution.exceptions.ValidationException;
import no.cicero.testframework.execution.validation.*;
import no.cicero.testframework.model.testcase.ClientObject;
import no.cicero.testframework.reporting.Reporting;
import no.cicero.testframework.reporting.ReportingStep;
import no.cicero.testframework.reporting.ReportingTestCase;
import no.cicero.testframework.reporting.ReportingUseCase;
import org.apache.commons.io.FileUtils;

import java.io.*;
import java.util.List;
import java.util.Map;

public class HtmlDetailedReport extends AbstractReport{
	private static final String FILES_FOLDER_CSS = "css";
	private static final String FILES_FOLDER_RESULT = "uc";

	private static final String FILES_BLANK = "blank.html";
	private static final String FILES_INDEX = "index.html";
	private static final String FILES_MENU = "menu.html";
	private static final String FILES_CSS = "style.css";

	private static final String HTML_BASIC = "" +
			"<html>\n"      +
			"   <head>\n"   +
			"       <title>Test Report</title>\n"   +
			"       <link rel='stylesheet' type='text/css' href='../[CSS_FOLDER]/[CSS_FILE]' />\n" +
			"   </head>\n"      +
			"   <body>\n"       +
			"   </body>\n"      +
			"</html>\n";

    private static String HEADER = "" +
            "<html>\n" +
            "   <head>\n" +
            "       <title>Test Framework Html Report</title>\n" +
            "       <link rel='stylesheet' type='text/css' href='../css/style.css' />\n" +
		    "       <META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n" +
            "   </head>\n\n" +
            "   <body style='font-family:Arial, Helvetica, sans-serif;'>\n";

    private static String FOOTER = "" +
            "   </body>\n" +
            "</html>";


    private Reporting reporting;
	
    private File outputDirectory;
    private File templateDirectory;
    private File cssDirectory;
    private File resultDirectory;

    private PrintWriter mainFrameWriter;
    private PrintWriter testCaseFrameWriter;
    private PrintWriter menuFrameWriter;

    private StringBuilder stepBuffer;
    private StringBuilder menuBuffer;

	public HtmlDetailedReport(File outputDirectory, File templateDirectory) throws IOException {
		this.outputDirectory = outputDirectory;
		this.templateDirectory = templateDirectory;
        mainFrameWriter = prepareFiles();
    }

    private PrintWriter prepareFiles() throws IOException {
        cssDirectory = new File(outputDirectory,FILES_FOLDER_CSS);
        FileUtils.forceMkdir(cssDirectory);

		resultDirectory = new File(outputDirectory, FILES_FOLDER_RESULT);
        FileUtils.forceMkdir(resultDirectory);
        
		FileUtils.writeStringToFile(new File(outputDirectory, FILES_BLANK), replaceHeader(HTML_BASIC,FILES_FOLDER_CSS,FILES_CSS));

		File file;

		file = new File(templateDirectory, FILES_CSS);
		if(file.exists())
			FileUtils.copyFileToDirectory(file, cssDirectory);

        File mainFrame = new File(outputDirectory, FILES_INDEX);
        return createWriter(mainFrame);
    }

    private static String replaceHeader(String text, String folder, String file) {
        text.replaceFirst("[CSS_FOLDER]", folder);
        text.replaceFirst("[CSS_FILE]", file);
        return text;
    }

	private static PrintWriter createWriter(File f)  throws IOException {
		return new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(f), "utf-8")));
	}

	@Override
    public void reportStarted(Reporting reporting) throws IOException {
        this.reporting = reporting;

        startReport();
        startReportMenu();
    }

    private void startReport(){
        mainFrameWriter.println("<html>");
        mainFrameWriter.println("   <head>");
        mainFrameWriter.println("       <title>Test Framework</title>");
        mainFrameWriter.println("       <link rel='stylesheet' type='text/css' href='styles.css' />");
        mainFrameWriter.println("   </head>");
        mainFrameWriter.println("   <frameset cols ='400,*'>");
        mainFrameWriter.println("       <frame name='_menu'  src='uc/menu.html' >");
        mainFrameWriter.println("       <frame name='_data'  src='blank.html' >");
        mainFrameWriter.println("   </frameset>");
        mainFrameWriter.println("</html>");
        mainFrameWriter.close();
    }

    private void startReportMenu() throws IOException{
        menuFrameWriter = createWriter(new File(resultDirectory, FILES_MENU));
        menuBuffer = new StringBuilder();
        menuBuffer.append(HEADER);
        menuBuffer.append("<table width='100%' class='main'>");

    }

	@Override
    public void reportFinished() {
        endReportMenu(reporting.getFailedStepsCount(), reporting.getTotalStepsCount());
        reporting = null;
    }

    private void endReportMenu(int failedSteps, int totalSteps){
        menuBuffer.append("<tr><td colspan='3'>&nbsp;</td></tr>" +
                          "<tr>" +
                          "     <td class='header'><div align='left'><b>&nbsp;&nbsp;Total for Run<b></div></td>" +
                          "     <td class='number'>"+failedSteps+"</td>" +
                          "     <td class='number'>"+totalSteps+"</td>" +
                          "     </tr>");
        menuBuffer.append("</table>");
        menuBuffer.append(FOOTER);
        menuFrameWriter.println(menuBuffer);
        menuBuffer = null;

        menuFrameWriter.close();
    }

	@Override
    public void useCaseStarted(ReportingUseCase useCase) {
        startReportMenuUseCase(useCase.getUseCaseName(),useCase.getUseCaseShortDescription());
    }

    private void startReportMenuUseCase(String ucName, String ucDescription){
        menuBuffer.append(" <tr>" +
                          "     <td class='header' colspan='3'>\n" +
                          "         <b>\n" +
                          "             <div style='width:95%; overflow:hidden;'>" + ucName + " - "+ ucDescription +"</div>\n" +
                          "         </b>\n" +
                          "     </td>\n" +
                          " </tr>\n");
    }

	@Override
    public void useCaseSucceeded(ReportingUseCase useCase) {
        endReportMenuUseCase(useCase.getUseCaseName(), useCase.getFailedStepsCount(), useCase.getTotalStepsCount());
    }

    private void endReportMenuUseCase(String ucName, int failedSteps, int totalSteps){
        menuBuffer.append(" <tr>" +
                          "     <td class='header' ><b>Total for: "+ucName+"</b></td>" +
                          "     <td class='number'>"+failedSteps+"</td>" +
                          "     <td class='number'>"+totalSteps+"</td>" +
                          " </tr><tr><td colspan='3'>&nbsp;</td></tr>");
    }

	@Override
    public void testCaseStarted(ReportingTestCase testCase) throws IOException {
        String filename = reporting.getCurrentUseCase().getUseCaseName().replaceFirst(".xml", "_") + testCase.getTestCaseFileName()+".html";
        
        startReportTestCase( filename );
        startReportTestCaseContent();
        startReportMenuTestCase(filename, testCase.getTestCaseName());
    }

    private void startReportTestCase(String filename) throws IOException {
        File testCaseFrame = new File(resultDirectory, filename);
        testCaseFrameWriter = createWriter(testCaseFrame);
    }

    private void startReportTestCaseContent() {
        stepBuffer = new StringBuilder();
        stepBuffer.append(HEADER);
    }

    private void startReportMenuTestCase(String url, String tcName){
        menuBuffer.append(" <tr class='results'>" +
                          "     <td>&nbsp;&nbsp;&nbsp;<a href='"+url+"' target='_data'>"+ tcName+" </a></td>\n ");
    }

	@Override
	public void testCaseSucceeded(ReportingTestCase testCase) {
        String classType = (testCase.getFailedStepsCount() == 0) ? "true" : "false";

        endReportTestCaseContent();
        endReportTestCase();
        endReportMenuTestCase(classType, testCase.getFailedStepsCount(), testCase.getTotalStepsCount());
    }
	
	@Override
    public void testCaseFailed(ReportingTestCase testCase, Exception ex) {
        endReportTestCaseContent();
        endReportTestCase(ex);
        endReportMenuTestCase("false", testCase.getFailedStepsCount(), testCase.getTotalStepsCount());
    }

    private void endReportTestCaseContent() {
        stepBuffer.append(FOOTER);
    }

    private void endReportTestCase(){
        testCaseFrameWriter.println(stepBuffer);
        stepBuffer = null;
        testCaseFrameWriter.close();
    }

    private void endReportTestCase(Exception error){
        testCaseFrameWriter.println(stepBuffer);
        stepBuffer = null;
        testCaseFrameWriter.println(error.getMessage());
        testCaseFrameWriter.println(error.getStackTrace().toString());
        testCaseFrameWriter.close();
    }

    private void endReportMenuTestCase(String classType, int failedSteps, int totalSteps){
        menuBuffer.append("     <td align='center' class='" + classType + "'>"+ failedSteps +"</td>\n" +
                          "     <td align='center'>"+ totalSteps +"</td>\n" +
                          " </tr>\n");
    }

	@Override
    public void stepSuccess(ReportingStep step) {
        stepEnd(step,"true");
    }

	@Override
    public void stepFailed(ReportingStep step) {
        stepEnd(step,"false");
    }

    private void stepEnd(ReportingStep step, String stepResult) {
        addReportTestCaseStep(step, stepResult);
    }

	private String formatFailure(ReportingStep step){
		ExecutionException failure = step.getFailure();
		String message = "";

		if(failure != null){
			if(failure.getMessage() != null)
				message += failure.getMessage();
			if(! (failure instanceof ValidationException))
				message += " " + failure.toString();
			else
				message += formatValidationMessage((ValidationException) failure);
		}

		return message;
	}

	private static String formatValidationMessage(ValidationException ex){
		StringBuilder ret = new StringBuilder();
		for(ValidationResult res: ex.getResults()){
			ret.append(formatValidationResult(res));
		}

		return ret.toString();
	}

	private static String formatValidationResult(ValidationResult res){
		if(res instanceof ExpectedNegative){
			return "<b>Expected</b>: <font color='blue'>negative result</font><br><b>Received</b>: <font color='red'>positive result</font>";
		} else if (res instanceof ExpectedPositive){
			return "<b>Expected</b>: <font color='blue'>positive result</font><br><b>Received</b>: <font color='red'>negative result</font>";
		} else if (res instanceof MissedField){
			MissedField res_ = (MissedField) res;
			return "<b><font color='red'>Missed field</font>: " + res_.getFieldName() + "</b><br>";
		} else if (res instanceof ValueDiffers){
			ValueDiffers res_ = (ValueDiffers) res;
			return
				"<b><font color='red'>" + res_.getFieldName() + "</font>: "
				+ res_.getResult() + " (<font color='blue'> " + res_.getExpectedResult() + "</font> )</b><br>"
			;
		} else if (res instanceof ArrayDiffers){
			ArrayDiffers res_ = (ArrayDiffers) res;
			return "<b><font color='red'>Result's array:</font>" + res_.getFieldName() + " has length other than expected</b><br>";
		} else if (res instanceof ArrayFieldDiffers){
			ArrayFieldDiffers res_ = (ArrayFieldDiffers) res;
			return
				"<b><font color='red'>Array Field:" + res_.getFieldName() + "'[" + res_.getIndex() + "] </font> : "
				+ res_.getResult() + " (<font color='blue'> " + res_.getExpectedResult() + "</font> )</b><br>"
			;
		} else if (res instanceof UnknownValue){
			UnknownValue res_ = (UnknownValue) res;
			return 
				"<b><font color='red'>Field: " + res_.getFieldName() + " - has unknown type or type that differs from expected " + " </font>: "
				+ res_.getResult() + " (<font color='blue'> " + res_.getExpectedResult() + "</font> )</b><br>";
		}

		return "Unknown Validation Result";
	}

    private static String clientObjectToHtml(ClientObject obj, String tab){
        StringBuilder out = new StringBuilder();
        if(obj != null){
            for(Map.Entry<String, Object> field: obj.getFields().entrySet()){
                Object val = field.getValue();
                if(val instanceof String || val instanceof ClientObject){
                    out.append(clientObjectToHtmlSimple(field.getKey(), val,tab));
                } else if(val instanceof List){
                    for(Object el: (List) val){
                        if(el instanceof String || el instanceof ClientObject){
                            out.append(clientObjectToHtmlSimple(field.getKey(), el, tab));
                        }
                    }
                }
            }
        }
        return out.toString();
    }

    private static String clientObjectToHtmlSimple(String fieldName, Object obj, String tab){
        String val = fieldName;
        if(obj == null){
            return "null<br>";
        } else if(obj instanceof String){
            return tab + val + " : " +(String)obj + "<br>";
        } else if(obj instanceof ClientObject){
            String res = clientObjectToHtml((ClientObject) obj, tab + "&nbsp;&nbsp;&nbsp;");
            if (!res.matches("")) {
                return tab + "&lt;"+ val + "&gt;<br>" + res + tab + "&lt;/"+val+"&gt;<br>";
            } else {
                return tab + val + " : <b>null</b><br>";
            }
        } else
            return "null";
    }

    private void addReportTestCaseStep(ReportingStep step, String stepResult){
        stepBuffer.append("     <table width='700' border='0' cellspacing='0' cellpadding='0' align='center' class='"+stepResult+"'>\n");

        stepBuffer.append("        <tr>\n");
        stepBuffer.append("           <td align='center' height='25' colspan='2'>");
        stepBuffer.append("                      <b>"+step.getStep().getMethod()+": "+step.getStep().getWebservice()+"</b>");
        stepBuffer.append("                      <font color='blue'>"+step.getStep().getDescription()+"</font>");
        stepBuffer.append("           </td>\n");
        stepBuffer.append("        </tr>\n");
        stepBuffer.append("        <tr>\n");
        stepBuffer.append("            <td height='20' class='header'><b>SOAP Input Parameters</b></td>\n");
        stepBuffer.append("            <td height='20' class='header'><b>SOAP Validation Parameters</b></td>\n");
        stepBuffer.append("        </tr>\n");

        stepBuffer.append("        <tr>\n");
        stepBuffer.append("            <td class='data'>");
        stepBuffer.append("                 <div style='overflow:hidden;margin:10px 0;'>");

		stepBuffer.append(clientObjectToHtml( step.getStep().getArguments(),"" ));

        stepBuffer.append("                        <div>");
        stepBuffer.append("            </td>\n");
        stepBuffer.append("            <td class='data'>");
        stepBuffer.append("                 <div style='overflow:hidden;margin:10px 0;'>");

        stepBuffer.append(clientObjectToHtml( step.getStep().getExpectedResult(),"" ));

        stepBuffer.append("                 <div>");
        stepBuffer.append("            </td>\n");
        stepBuffer.append("       </tr>\n");
        stepBuffer.append("       <tr>\n");
        stepBuffer.append("             <td height='20' align='center' style='border-top:1px solid black;' colspan='2'><b>Expected: " );
        stepBuffer.append("                 <span style='color:blue'>"+Boolean.toString(step.getStep().isPositive())+"</span></b>");
        stepBuffer.append("             </td>\n");
        stepBuffer.append("       </tr>\n");
        stepBuffer.append("       <tr>\n");
        stepBuffer.append("             <td height='20' class='header'><b>SOAP Result</b></td>\n");
        stepBuffer.append("             <td height='20' class='header'><b>Validation Failed On</b></td>\n");
        stepBuffer.append("       </tr>\n");
        stepBuffer.append("       <tr>\n");
        stepBuffer.append("             <td class='data' width='350px'>\n");
        stepBuffer.append("                 <div style='overflow:hidden;margin:10px 0;'>");

        stepBuffer.append(clientObjectToHtml( step.getResponse(),"" ));
        stepBuffer.append("                 <div>\n");
        stepBuffer.append("             </td>\n");
        stepBuffer.append("             <td class='data'>\n");
        stepBuffer.append("                 <div style='overflow:hidden;margin:10px 0;'>\n");
        stepBuffer.append(formatFailure(step));
        stepBuffer.append("                 </div>\n");
        stepBuffer.append("             </td>\n");
        stepBuffer.append("        </tr>\n");
        stepBuffer.append("     </table><br>\n");
    }
}
